/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/
#include <dlfcn.h>
#include "include/diff.h"
#include "include/sim.h"
#include <stdint.h>

CPUState cpu_state;

void (*ref_difftest_memcpy)(word_t addr, void *buf, word_t n, bool direction) = NULL;
void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
void (*ref_difftest_exec)(word_t n) = NULL;
void (*ref_difftest_raise_intr)(word_t NO) = NULL;

#ifdef CONFIG_DIFFTEST
//bool isa_difftest_checkregs(CPU_state *ref_r, vaddr_t pc);
static bool is_skip_ref = false;
static int skip_dut_nr_inst = 0;

// this is used to let ref skip instructions which
// can not produce consistent behavior with NEMU
void difftest_skip_ref() {
  is_skip_ref = true;
  // If such an instruction is one of the instruction packing in QEMU
  // (see below), we end the process of catching up with QEMU's pc to
  // keep the consistent behavior in our best.
  // Note that this is still not perfect: if the packed instructions
  // already write some memory, and the incoming instruction in NEMU
  // will load that memory, we will encounter false negative. But such
  // situation is infrequent.
  skip_dut_nr_inst = 0;
}

// this is used to deal with instruction packing in QEMU.
// Sometimes letting QEMU step once will execute multiple instructions.
// We should skip checking until NEMU's pc catches up with QEMU's pc.
// The semantic is
//   Let REF run `nr_ref` instructions first.
//   We expect that DUT will catch up with REF within `nr_dut` instructions.
void difftest_skip_dut(int nr_ref, int nr_dut) {
  skip_dut_nr_inst += nr_dut;

  while (nr_ref -- > 0) {
    ref_difftest_exec(1);
  }
}

void init_difftest(char *ref_so_file, word_t img_size, int port) {
  assert(ref_so_file != NULL);
  assert(img_size >= 0);
  printf("[difftest] initializing diifferential testing, the ref-so-file is %s, img-size is %d\n", ref_so_file, img_size);
  void *handle;
  handle = dlopen(ref_so_file, RTLD_LAZY);
  assert(handle);

  ref_difftest_memcpy = (void (*)(word_t, void*, word_t, bool)) dlsym(handle, "difftest_memcpy");
  assert(ref_difftest_memcpy);

  ref_difftest_regcpy = (void (*)(void*, bool)) dlsym(handle, "difftest_regcpy");
  assert(ref_difftest_regcpy);

  ref_difftest_exec = (void (*)(word_t)) dlsym(handle, "difftest_exec");
  assert(ref_difftest_exec);

  ref_difftest_raise_intr = (void (*)(word_t)) dlsym(handle, "difftest_raise_intr");
  assert(ref_difftest_raise_intr);

  void (*ref_difftest_init)(int) = (void (*)(int)) dlsym(handle, "difftest_init");
  assert(ref_difftest_init);

  cpu_state.pc = 0x80000000;
  printf("[difftest] initialized PC = 0x%x\n", cpu_state.pc);

  ref_difftest_init(port);
  ref_difftest_memcpy(MEM_START, guest_to_host(MEM_START), img_size, DIFFTEST_TO_REF);
  ref_difftest_regcpy(&cpu_state, DIFFTEST_TO_REF);
}

bool isa_difftest_checkregs(void) {

  CPUState ref_r;
  ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);
  assert(&ref_r != NULL);

  for (int i = 0; i < NR_GPRs; i++) {
    if (ref_r.gpr[i] != cpu_state.gpr[i]) {
      printf("[difftest] ERROR: GPR[%d] is different at PC 0x%x, ref_r is 0x%x, dut is 0x%x\n", i, cpu_state.pc, ref_r.gpr[i], cpu_state.gpr[i]);
      return false;
    }
  }
  if (ref_r.pc != cpu_state.pc) {
    printf("[difftest] ERROR: PC is different, ref_r is 0x%x, dut is 0x%x\n", ref_r.pc, cpu_state.pc);
    return false;
  }
  return true;
}

// void difftest_step(paddr_t pc, paddr_t npc) {
//   CPUState ref_r;

//   if (skip_dut_nr_inst > 0) {
//     ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);
//     if (ref_r.pc == npc) {
//       skip_dut_nr_inst = 0;
//       checkregs(&ref_r, npc);
//       return;
//     }
//     skip_dut_nr_inst --;
//     if (skip_dut_nr_inst == 0)
//       panic("can not catch up with ref.pc = " FMT_WORD " at pc = " FMT_WORD, ref_r.pc, pc);
//     return;
//   }

//   if (is_skip_ref) {
//     // to skip the checking of an instruction, just copy the reg state to reference design
//     ref_difftest_regcpy(&cpu, DIFFTEST_TO_REF);
//     is_skip_ref = false;
//     return;
//   }

//   ref_difftest_exec(1);
//   ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);

//   checkregs(&ref_r, pc);
// }



#else
void init_difftest(char *ref_so_file, long img_size, int port) { }
#endif
